// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 1993-1997  Microsoft Corporation.  All Rights Reserved.
//
//  MODULE:   service.c
//
//  PURPOSE:  Implements functions required by all services
//            windows.
//
//  FUNCTIONS:
//    main(int argc, char **argv);
//    service_ctrl(DWORD dwCtrlCode);
//    service_main(DWORD dwArgc, LPTSTR *lpszArgv);
//    CmdInstallService();
//    CmdRemoveService();
//    ControlHandler ( DWORD dwCtrlType );
//    GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );
//
//  COMMENTS:
//
//  AUTHOR: Craig Link - Microsoft Developer Support
//

/*
 * modified Mar.07, 2002 by Feng Qin <fqin@ncsa.uiuc.edu>
 *          Mar.15, 2002
 *
 * removed some functions we don't use at all
 * add code to start the service immediately after service is installed
 * 
 * $Id: service.c,v 1.1.1.1 2004/05/18 01:50:44 kgibbs Exp $
 */

#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <tchar.h>

#include "service.h"
#include "Settings.hpp"
#include "PerfSocket.hpp"

// internal variables
SERVICE_STATUS ssStatus; // current status of the service
SERVICE_STATUS_HANDLE sshStatusHandle;
DWORD dwErr = 0;
TCHAR szErr[256];

//
//  FUNCTION: service_main
//
//  PURPOSE: To perform actual initialization of the service
//
//  PARAMETERS:
//    dwArgc   - number of command line arguments
//    lpszArgv - array of command line arguments
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    This routine performs the service initialization and then calls
//    the user defined ServiceStart() routine to perform majority
//    of the work.
//
void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv)
{
    // register our service control handler:
    //
    sshStatusHandle = RegisterServiceCtrlHandler(TEXT(SZSERVICENAME), service_ctrl);

    if (!sshStatusHandle)
        goto clean;

    // SERVICE_STATUS members that don't change in example
    //
    ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ssStatus.dwServiceSpecificExitCode = 0;

    // report the status to the service control manager.
    //
    if (!ReportStatusToSCMgr(SERVICE_START_PENDING, // service state
                             NO_ERROR,              // exit code
                             3000))                 // wait hint
        goto clean;


    ServiceStart(dwArgc, lpszArgv);

clean:

    // try to report the stopped status to the service control manager.
    //
    if (sshStatusHandle)
        (VOID) ReportStatusToSCMgr(SERVICE_STOPPED, dwErr, 0);

    return;
}



//
//  FUNCTION: service_ctrl
//
//  PURPOSE: This function is called by the SCM whenever
//           ControlService() is called on this service.
//
//  PARAMETERS:
//    dwCtrlCode - type of control requested
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
VOID WINAPI service_ctrl(DWORD dwCtrlCode)
{
    // Handle the requested control code.
    //
    switch (dwCtrlCode)
    {
        // Stop the service.
        //
        // SERVICE_STOP_PENDING should be reported before
        // setting the Stop Event - hServerStopEvent - in
        // ServiceStop().  This avoids a race condition
        // which may result in a 1053 - The Service did not respond...
        // error.
        case SERVICE_CONTROL_STOP:
            ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0);
            ServiceStop();

            return;

            // Update the service status.
            //
        case SERVICE_CONTROL_INTERROGATE:
            break;

            // invalid control code
            //
        default:
            break;
    }
    ReportStatusToSCMgr(SERVICE_STOPPED, NO_ERROR, 0);
    //    ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
}



//
//  FUNCTION: ReportStatusToSCMgr()
//
//  PURPOSE: Sets the current status of the service and
//           reports it to the Service Control Manager
//
//  PARAMETERS:
//    dwCurrentState - the state of the service
//    dwWin32ExitCode - error code to report
//    dwWaitHint - worst case estimate to next checkpoint
//
//  RETURN VALUE:
//    TRUE  - success
//    FALSE - failure
//
//  COMMENTS:
//
BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
    static DWORD dwCheckPoint = 1;
    BOOL fResult = TRUE;


    if (dwCurrentState == SERVICE_START_PENDING)
        ssStatus.dwControlsAccepted = 0;
    else
        ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;

    ssStatus.dwCurrentState = dwCurrentState;
    ssStatus.dwWin32ExitCode = dwWin32ExitCode;
    ssStatus.dwWaitHint = dwWaitHint;

    if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
        ssStatus.dwCheckPoint = 0;
    else
        ssStatus.dwCheckPoint = dwCheckPoint++;


    // Report the status of the service to the service control manager.
    //
    if (!(fResult = SetServiceStatus(sshStatusHandle, &ssStatus)))
    {
        AddToMessageLog(TEXT("SetServiceStatus"));
    }
    return fResult;
}



//
//  FUNCTION: AddToMessageLog(LPTSTR lpszMsg)
//
//  PURPOSE: Allows any thread to log an error message
//
//  PARAMETERS:
//    lpszMsg - text for message
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
VOID AddToMessageLog(LPTSTR lpszMsg)
{
    TCHAR szMsg[256];
    HANDLE hEventSource;
    LPTSTR lpszStrings[2];


    dwErr = GetLastError();

    // Use event logging to log the error.
    //
    hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME));

    printf(lpszMsg);

    _stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr);
    lpszStrings[0] = szMsg;
    lpszStrings[1] = lpszMsg;

    if (hEventSource != NULL)
    {
        ReportEvent(hEventSource,          // handle of event source
                    EVENTLOG_ERROR_TYPE,   // event type
                    0,                     // event category
                    0,                     // event ID
                    NULL,                  // current user's SID
                    2,                     // strings in lpszStrings
                    0,                     // no bytes of raw data
                    (LPCSTR *)lpszStrings, // array of error strings
                    NULL);                 // no raw data

        (VOID) DeregisterEventSource(hEventSource);
    }
}




///////////////////////////////////////////////////////////////////
//
//  The following code handles service installation and removal
//
//
//  FUNCTION: CmdInstallService()
//
//  PURPOSE: Installs the service and Starts it
//
//  PARAMETERS:
//    argc: number of arguments
//	argv: all of the arguments including the program's name
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
void CmdInstallService(int argc, char **argv)
{
    SC_HANDLE schService;
    SC_HANDLE schSCManager;

    TCHAR szPath[512];

    if (GetModuleFileName(NULL, szPath, 512) == 0)
    {
        _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256));
        return;
    }

    schSCManager = OpenSCManager(NULL,                 // machine (NULL == local)
                                 NULL,                 // database (NULL == default)
                                 SC_MANAGER_ALL_ACCESS // access required
    );
    if (schSCManager)
    {
        schService = OpenService(schSCManager, TEXT(SZSERVICENAME), SERVICE_ALL_ACCESS);
        if (!schService)
        {
            schService = CreateService(schSCManager,               // SCManager database
                                       TEXT(SZSERVICENAME),        // name of service
                                       TEXT(SZSERVICEDISPLAYNAME), // name to display
                                       SERVICE_ALL_ACCESS,         // desired access
                                       SERVICE_WIN32_OWN_PROCESS,  // service type
                                       SERVICE_DEMAND_START,       // start type
                                       SERVICE_ERROR_NORMAL,       // error control type
                                       szPath,                     // service's binary
                                       NULL,                       // no load ordering group
                                       NULL,                       // no tag identifier
                                       TEXT(SZDEPENDENCIES),       // dependencies
                                       NULL,                       // LocalSystem account
                                       NULL);                      // no password
        }
        else
        {
            _tprintf(TEXT("%s already installed.\n"), TEXT(SZSERVICEDISPLAYNAME));
        }
        if (schService)
        {
            if (QueryServiceStatus(schService, &ssStatus))
            {
                int rc;
                if (ssStatus.dwCurrentState == SERVICE_STOPPED)
                {
                    rc = StartService(schService, argc - 1, (LPCSTR *)(argv + 1));
                }


                if (rc != 0)
                    _tprintf(TEXT("%s started.\n"), TEXT(SZSERVICEDISPLAYNAME));
            }

            CloseServiceHandle(schService);
        }
        else
        {
            _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 256));
        }

        CloseServiceHandle(schSCManager);
    }
    else
        _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr, 256));
}



//
//  FUNCTION: CmdRemoveService()
//
//  PURPOSE: Stops and removes the service
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE: service exists and is removed
//	FALSE: service doesn't exist
//
//  COMMENTS:
//
BOOL CmdRemoveService()
{
    BOOL isExist = FALSE;
    SC_HANDLE schService;
    SC_HANDLE schSCManager;

    schSCManager = OpenSCManager(NULL,                 // machine (NULL == local)
                                 NULL,                 // database (NULL == default)
                                 SC_MANAGER_ALL_ACCESS // access required
    );
    if (schSCManager)
    {
        schService = OpenService(schSCManager, TEXT(SZSERVICENAME), SERVICE_ALL_ACCESS);

        if (schService)
        {
            isExist = TRUE;
            // try to stop the service
            if (ControlService(schService, SERVICE_CONTROL_STOP, &ssStatus))
            {
                _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME));
                Sleep(1000);

                while (QueryServiceStatus(schService, &ssStatus))
                {
                    if (ssStatus.dwCurrentState == SERVICE_STOP_PENDING)
                    {
                        _tprintf(TEXT("."));
                        Sleep(1000);
                    }
                    else
                        break;
                }

                if (ssStatus.dwCurrentState == SERVICE_STOPPED)
                    _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME));
                else
                    _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME));
            }

            // now remove the service
            if (DeleteService(schService))
                _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME));
            else
                _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(szErr, 256));


            CloseServiceHandle(schService);
        }

        CloseServiceHandle(schSCManager);
    }
    else
        _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr, 256));

    return isExist;
}

//
//  FUNCTION: CmdStartService()
//
//  PURPOSE: Start service if it exists
//
//  PARAMETERS:
//    argc: number of arguments
//	argv: arguments including program's name
//
//  RETURN VALUE:
//    TRUE: service exists and is started
//	FALSE: service doesn't exist
//
//  COMMENTS:
//
BOOL CmdStartService(int argc, char **argv)
{
    BOOL isExist = FALSE;
    SC_HANDLE schService;
    SC_HANDLE schSCManager;

    schSCManager = OpenSCManager(NULL,                 // machine (NULL == local)
                                 NULL,                 // database (NULL == default)
                                 SC_MANAGER_ALL_ACCESS // access required
    );
    if (schSCManager)
    {
        schService = OpenService(schSCManager, TEXT(SZSERVICENAME), SERVICE_ALL_ACCESS);

        if (schService)
        {
            isExist = TRUE;
            if (QueryServiceStatus(schService, &ssStatus))
            {
                int rc;
                if (ssStatus.dwCurrentState == SERVICE_STOPPED)
                {
                    rc = StartService(schService, argc - 1, (LPCSTR *)(argv + 1));
                }


                if (rc != 0)
                    _tprintf(TEXT("%s started.\n"), TEXT(SZSERVICEDISPLAYNAME));
            }
            CloseServiceHandle(schService);
        }
        CloseServiceHandle(schSCManager);
    }
    else
        _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr, 256));

    return isExist;
}




//
//  FUNCTION: GetLastErrorText
//
//  PURPOSE: copies error message text to string
//
//  PARAMETERS:
//    lpszBuf - destination buffer
//    dwSize - size of buffer
//
//  RETURN VALUE:
//    destination buffer
//
//  COMMENTS:
//
LPTSTR GetLastErrorText(LPTSTR lpszBuf, DWORD dwSize)
{
    DWORD dwRet;
    LPTSTR lpszTemp = NULL;

    dwRet = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                          NULL, GetLastError(), LANG_NEUTRAL, (LPTSTR)&lpszTemp, 0, NULL);

    // supplied buffer is not long enough
    if (!dwRet || ((long)dwSize < (long)dwRet + 14))
        lpszBuf[0] = TEXT('\0');
    else
    {
        lpszTemp[lstrlen(lpszTemp) - 2] = TEXT('\0'); //remove cr and newline character
        _stprintf(lpszBuf, TEXT("%s (0x%x)"), lpszTemp, GetLastError());
    }

    if (lpszTemp)
        LocalFree((HLOCAL)lpszTemp);

    return lpszBuf;
}

/*--------------------------------------------------------------------
 * ServiceStart
 *
 * each time starting the service, this is the entry point of the service.
 * Start the service, certainly it is on server-mode
 * 
 *-------------------------------------------------------------------- */
VOID ServiceStart(DWORD dwArgc, LPTSTR *lpszArgv)
{

    // report the status to the service control manager.
    //
    if (!ReportStatusToSCMgr(SERVICE_START_PENDING, // service state
                             NO_ERROR,              // exit code
                             3000))                 // wait hint
        goto clean;

    thread_Settings *ext_gSettings = new thread_Settings;

    // Initialize settings to defaults
    Settings_Initialize(ext_gSettings);
    // read settings from environment variables
    Settings_ParseEnvironment(ext_gSettings);
    // read settings from command-line parameters
    Settings_ParseCommandLine(dwArgc, lpszArgv, ext_gSettings);

    // report the status to the service control manager.
    //
    if (!ReportStatusToSCMgr(SERVICE_START_PENDING, // service state
                             NO_ERROR,              // exit code
                             3000))                 // wait hint
        goto clean;

    // if needed, redirect the output into a specified file
    if (!isSTDOUT(ext_gSettings))
    {
        redirect(ext_gSettings->mOutputFileName);
    }

    // report the status to the service control manager.
    //
    if (!ReportStatusToSCMgr(SERVICE_START_PENDING, // service state
                             NO_ERROR,              // exit code
                             3000))                 // wait hint
        goto clean;

    // initialize client(s)
    if (ext_gSettings->mThreadMode == kMode_Client)
    {
        client_init(ext_gSettings);
    }

    // start up the reporter and client(s) or listener
    {
        thread_Settings *into = NULL;
#ifdef HAVE_THREAD
        Settings_Copy(ext_gSettings, &into);
        into->mThreadMode = kMode_Reporter;
        into->runNow = ext_gSettings;
#else
        into = ext_gSettings;
#endif
        thread_start(into);
    }

    // report the status to the service control manager.
    //
    if (!ReportStatusToSCMgr(SERVICE_RUNNING, // service state
                             NO_ERROR,        // exit code
                             0))              // wait hint
        goto clean;

clean:
    // wait for other (client, server) threads to complete
    thread_joinall();
}


//
//  FUNCTION: ServiceStop
//
//  PURPOSE: Stops the service
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    If a ServiceStop procedure is going to
//    take longer than 3 seconds to execute,
//    it should spawn a thread to execute the
//    stop code, and return.  Otherwise, the
//    ServiceControlManager will believe that
//    the service has stopped responding.
//
VOID ServiceStop()
{
#ifdef HAVE_THREAD
    Sig_Interupt(1);
#else
    sig_exit(1);
#endif
}

#endif
